package com.yukefms.rule.state;

import com.yukefms.engine.running.*;
import com.yukefms.jointpoint.BodyPointCloud;

import com.yukefms.engine.running.RunningEvent ;
import com.yukefms.engine.running.RunningContext;
import com.yukefms.rule.interfaces.BARule;
import com.yukefms.rule.interfaces.BAStageRule;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @Description
 * @Author Ping
 * @Date 2021/6/22 22:36
 **/
public abstract class BARuleState {

    public static Logger logger = LogManager.getLogger(BARuleState.class);
    protected BAStageRule relatedRule ;


    public BARuleState(final BAStageRule relatedRule) {
        if (relatedRule != null) {
            this.relatedRule = relatedRule;
            this.relatedRule.setRuleState(this);
        }

    }

    public BARule getRelatedRule() {
        return this.relatedRule;
    }

    public void setRelatedRule(final BAStageRule relatedRule) {

        this.relatedRule = relatedRule;
        //this.relatedRule.setRuleState(this);

    }

    /**
     * define a template method for reaction on read pointCloud.
     * @param pointCloud
     * @param context
     */
    public void doReaction(BodyPointCloud pointCloud, RunningContext context) {
        /**
         * After engine starts up, the logic for processing pointCloud
         * was defined as follows based on its transition system semantics at {@code BARule}:
         * For each read pointCloud at following cases:
         *
         * Case 1. <Abstract state>at  abstract state, following template method was defined for each rule states:
         * (NOTE, excluding UN_MATCHED, SUCCESS and FAILED states):
         *      <BEGIN>
         *      check if the performing time is out:
         *      if time is out,
         *            the rule state was transited to the unmatched state.
         *            return ;
         *
     *           check if the input is relevant.
     *           if it is not relevant,
     *                update inputErrorCounter
     *                if input error tolerant number arrives the maximal
     *                      transited to the unmatched state
     *                      return;
     *                 else
     *                     return for the next pointCloud.
         *       processing pointCloud at InActivated/Activated State.
         *       <END>.
         *
         * CASE 2. <In_activated state> the logic for process pointCloud at IN_ACTIVATED state was defined as follow:
         *
         *    check the rule is triggered.
         *    if it is :
         *        transition to ACTIVATED state
         *     else
         *        return for the next pointCloud
         *
         *CASE 3. <ACITVATED state>
         *     check the invariant of rule is followed.
         *     if it not,
         *          transition to the unmatched state
         *      else
         *          check the response was arrived
         *          if it is,
         *              transition to the success state.
         *           else
         *              return for the next pointCloud.
         *
         *CASE 4. <unmatched state> The logic for processing read pointCloud at UNMATCHED state was defined as follow:
         *
         *     update TrialCounter
         *     if trial is out:
         *         transition to FAILED state
         *     else
         *         reset Timer and input_error Counter
         *         transition to IN_ACTIVATED state
         *CASE 5. <final states>
         * The logic for processing read point Cloud at SUCCESS state or FAILED state was defined as follow:'
         *
         *     logger.info(the final result)
         *     returning the result to client and reset the context for the next rule. [implemented in next version]
         */

        /**
         * TASK to finish:
         * 1. should considering notifyRunningEventListener to response infor to the Front
         * 2. should make clear the relation between UnMatched state and Success and Failed state.
         * 3. the Success and Failed states should response their infor by eventlistener to the front.
         * 4. the whole transition system needs to be more clear.
         */
        context.updateTimer();
        if (context.isTimeout()) {
            /**
             * if the action performing time was expired, then the performing was failed.
             */
            this.relatedRule.setRuleState(RuleStateFlyWeight.getState(RuleStateFlyWeight.UN_MATCHED));
            this.relatedRule.doReaction(pointCloud, context);
            return ;
        }
        /*
        Then, check the pointCloud is relevant.
         */
        if (! context.areAllRelevant(pointCloud)) {
            context.updateInputErrorCounter();
            if (context.isInputError()) {
                context.notifyInputErrorEventListener(RunningEvent.buildEvent(EventCode.INPUT_ERROR, relatedRule.ruleName())
                                                    , context);
                this.relatedRule.setRuleState(RuleStateFlyWeight.getState(RuleStateFlyWeight.UN_MATCHED));
                this.relatedRule.doReaction(pointCloud, context);
                return ;
            }
            /**
             * for the inrelevant point cloud but did not over input-error thoreshold.
             */
           logger.error("the point cloud is not relevant, please ADJUST the distance between camera and body.") ;
           return ;
        }
        /**
         * hook method could be overrided by each substates.
         */

        processRelevantPointsCloud(pointCloud, context);

    }

    /**
     *
     * hook function which could be overrided by substates
     * @param pointCloud
     * @param context
     */
    protected void processRelevantPointsCloud(BodyPointCloud pointCloud, RunningContext context) {

    }

    public void toFailed(RunningContext context) {

    }
    protected void reset(RunningContext context) {

    }

    /**
     * get the score for current state.
     * The scoring is based the state the player could arrive.
     * @return
     */
    public double getScore() {
        return 0.0;
    }
}



